153 lines · 6.6 KB
1 ---
2 import Repo from '../../../layouts/Repo.astro';
3 import { apiGet, apiPost } from '../../../lib/api';
4
5 const { owner, repo } = Astro.params;
6 const cookie = Astro.request.headers.get('cookie') || '';
7
8 let repoData: any = null;
9 let webhooks: any[] = [];
10 let error = '';
11 let success = '';
12
13 try {
14 repoData = await apiGet(`/api/repos/${owner}/${repo}`, cookie);
15 try {
16 webhooks = await apiGet(`/api/repos/${owner}/${repo}/webhooks`, cookie);
17 } catch { /* may not have access */ }
18 } catch (e: any) {
19 error = e.message;
20 }
21
22 if (Astro.request.method === 'POST') {
23 try {
24 const formData = await Astro.request.formData();
25 const action = formData.get('action');
26
27 if (action === 'update') {
28 const description = formData.get('description') as string;
29 const isPrivate = formData.get('visibility') === 'private';
30
31 const res = await fetch(
32 `${import.meta.env.PUBLIC_API_URL || 'http://localhost:8321'}/api/repos/${owner}/${repo}/settings`,
33 {
34 method: 'PUT',
35 headers: { 'Content-Type': 'application/json', Cookie: cookie },
36 body: JSON.stringify({ description, is_private: isPrivate }),
37 }
38 );
39 if (!res.ok) throw new Error('Failed to update');
40 success = 'Settings updated.';
41 repoData = await res.json();
42 } else if (action === 'add_webhook') {
43 const url = formData.get('webhook_url') as string;
44 const secret = formData.get('webhook_secret') as string;
45 const events = formData.get('webhook_events') as string || 'push';
46 await apiPost(`/api/repos/${owner}/${repo}/webhooks`, { url, secret, events }, cookie);
47 success = 'Webhook added.';
48 webhooks = await apiGet(`/api/repos/${owner}/${repo}/webhooks`, cookie);
49 }
50
51 return Astro.redirect(`/${owner}/${repo}/settings`);
52 } catch (e: any) {
53 error = e.message;
54 }
55 }
56 ---
57
58 <Repo owner={owner!} repo={repo!} activeTab="settings">
59 {error && <div class="flash-error">{error}</div>}
60 {success && <div style="background: var(--success-bg); border: 1px solid var(--success-border); padding: 12px; border-radius: var(--radius); margin-bottom: 16px; font-size: 0.875rem; color: var(--success-text);">{success}</div>}
61
62 {repoData && (
63 <>
64 {/* General settings */}
65 <div class="card" style="margin-bottom: 24px;">
66 <h2 style="font-size: 1.125rem; margin-bottom: 16px;">General</h2>
67 <form method="POST">
68 <input type="hidden" name="action" value="update" />
69 <div class="form-group">
70 <label for="description">Description</label>
71 <input type="text" id="description" name="description" value={repoData.description || ''} />
72 </div>
73 <div class="form-group">
74 <label>Visibility</label>
75 <div style="display: flex; gap: 16px; margin-top: 4px;">
76 <label style="display: flex; align-items: center; gap: 6px; font-weight: normal; cursor: pointer;">
77 <input type="radio" name="visibility" value="public" checked={!repoData.is_private} /> Public
78 </label>
79 <label style="display: flex; align-items: center; gap: 6px; font-weight: normal; cursor: pointer;">
80 <input type="radio" name="visibility" value="private" checked={!!repoData.is_private} /> Private
81 </label>
82 </div>
83 </div>
84 <button type="submit" class="btn btn-primary">Save changes</button>
85 </form>
86 </div>
87
88 {/* Clone info */}
89 <div class="card" style="margin-bottom: 24px;">
90 <h2 style="font-size: 1.125rem; margin-bottom: 12px;">Clone URL</h2>
91 <code style="display: block; background: var(--bg); padding: 8px 12px; border-radius: var(--radius); border: 1px solid var(--border); font-size: 0.8125rem;">
92 git clone {new URL(Astro.request.url).origin}/{owner}/{repo}.git
93 </code>
94 </div>
95
96 {/* Webhooks */}
97 <div class="card" style="margin-bottom: 24px;">
98 <h2 style="font-size: 1.125rem; margin-bottom: 12px;">Webhooks</h2>
99
100 {webhooks.length > 0 && (
101 <div style="margin-bottom: 16px;">
102 {webhooks.map((wh: any) => (
103 <div style="display: flex; justify-content: space-between; align-items: center; padding: 8px 0; border-bottom: 1px solid var(--border); font-size: 0.875rem;">
104 <div>
105 <code>{wh.url}</code>
106 <span style="color: var(--text-muted); margin-left: 8px; font-size: 0.75rem;">
107 Events: {wh.events}
108 </span>
109 </div>
110 <span style={`font-size: 0.75rem; ${wh.is_active ? 'color: var(--accent);' : 'color: var(--danger);'}`}>
111 {wh.is_active ? 'active' : 'inactive'}
112 </span>
113 </div>
114 ))}
115 </div>
116 )}
117
118 <form method="POST">
119 <input type="hidden" name="action" value="add_webhook" />
120 <div class="form-group">
121 <label for="webhook_url">Payload URL</label>
122 <input type="text" id="webhook_url" name="webhook_url" required placeholder="https://example.com/webhook" />
123 </div>
124 <div style="display: flex; gap: 8px;">
125 <div class="form-group" style="flex: 1;">
126 <label for="webhook_secret">Secret <span style="color: var(--text-muted); font-weight: normal;">(optional)</span></label>
127 <input type="text" id="webhook_secret" name="webhook_secret" placeholder="HMAC signing secret" />
128 </div>
129 <div class="form-group" style="flex: 1;">
130 <label for="webhook_events">Events</label>
131 <input type="text" id="webhook_events" name="webhook_events" value="push,merge_request" />
132 </div>
133 </div>
134 <button type="submit" class="btn btn-primary">Add webhook</button>
135 </form>
136 </div>
137
138 {/* Danger zone */}
139 <div style="border: 1px solid var(--danger); border-radius: var(--radius); padding: 16px;">
140 <h2 style="font-size: 1.125rem; color: var(--danger); margin-bottom: 8px;">Danger Zone</h2>
141 <p style="font-size: 0.8125rem; color: var(--text-muted); margin-bottom: 12px;">
142 Once you delete a repository, there is no going back.
143 </p>
144 <button class="btn" style="border-color: var(--danger); color: var(--danger);"
145 onclick="if(confirm('Are you sure? This cannot be undone.')) { fetch(this.dataset.url, {method:'DELETE',credentials:'include'}).then(()=>location.href='/') }"
146 data-url={`${import.meta.env.PUBLIC_API_URL || 'http://localhost:8321'}/api/repos/${owner}/${repo}`}>
147 Delete this repository
148 </button>
149 </div>
150 </>
151 )}
152 </Repo>
153